package world.snowcrystal.configuration;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AnonymousAuthenticationProvider;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import world.snowcrystal.aa.RememberMeAuthenticationFailureHandler;
import world.snowcrystal.aa.RememberMeAuthenticationSuccessHandler;
import world.snowcrystal.aa.DefaultLogoutHandler;
import world.snowcrystal.aa.SUsernamePasswordAuthenticationFilter;
import world.snowcrystal.properties.AuthenticationProperties;

import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration {

    @Autowired
    private AuthenticationProperties authenticationProperties;

    @Autowired
    private UserDetailsService uds;


    @Bean
    public RememberMeAuthenticationSuccessHandler rememberMeAuthenticationSuccessHandler() {
        return new RememberMeAuthenticationSuccessHandler(authenticationProperties);
    }

    @Bean
    public RememberMeAuthenticationFailureHandler rememberMeAuthenticationFailureHandler() {
        return new RememberMeAuthenticationFailureHandler(authenticationProperties, "/");
    }

    @Bean
    public List<RequestMatcher> requestMatchers() {
        List<RequestMatcher> requestMatchers = new ArrayList<>();
        requestMatchers.add(new AntPathRequestMatcher("/authentication", "POST"));
        requestMatchers.add(new AntPathRequestMatcher("/user/this", "GET"));
        requestMatchers.add(new AntPathRequestMatcher("/logout", "POST"));
        requestMatchers.add(new AntPathRequestMatcher("/user/modification/in-place/**", "POST"));
        return requestMatchers;
    }


    @Bean
    public AuthenticationManager authenticationManager(PasswordEncoder pe,
                                                       AuthenticationEventPublisher aep) {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(pe);
        AnonymousAuthenticationProvider anonymousAuthenticationProvider = new AnonymousAuthenticationProvider("tianqing");
        daoAuthenticationProvider.setUserDetailsService(uds);
        daoAuthenticationProvider.setForcePrincipalAsString(false); // 必须为 false 否则 Controller 将无法获取 UserDetails 对象
        ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider, anonymousAuthenticationProvider);
        providerManager.setAuthenticationEventPublisher(aep);
        return providerManager;
    }


    @Bean
    @ConditionalOnClass(AuthenticationManager.class)
    public AbstractAuthenticationProcessingFilter processingFilter(AuthenticationManager manager,
                                                                   RememberMeAuthenticationSuccessHandler successHandler,
                                                                   List<RequestMatcher> requestMatchers,
                                                                   RememberMeAuthenticationFailureHandler failureHandler) {
        SUsernamePasswordAuthenticationFilter sUsernamePasswordAuthenticationFilter = new SUsernamePasswordAuthenticationFilter(manager);
        sUsernamePasswordAuthenticationFilter.setUsernameParameter("username");
        sUsernamePasswordAuthenticationFilter.setPasswordParameter("password");
        sUsernamePasswordAuthenticationFilter.setContinueChainBeforeSuccessfulAuthentication(false);
        sUsernamePasswordAuthenticationFilter.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(requestMatchers));
        sUsernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
        sUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(failureHandler);
        return sUsernamePasswordAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain customizedSecurityFilterChain(HttpSecurity http,DefaultLogoutHandler logoutHandler) throws Exception {

//        http.requestCache(conf->{
//            conf.requestCache(new HttpSessionRequestCache());
//        })

        http.sessionManagement(conf -> {
            conf.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
        });

        http.securityContext(conf -> {
            conf.securityContextRepository(new HttpSessionSecurityContextRepository())
                    .requireExplicitSave(true);
        });

        // 不需要再这里配置 CorsFilter Bean 只要被提供，就会被 SpringBoot 自动注入
        // 见 WebMvcConfiguration
        http.cors(Customizer.withDefaults())
                .csrf(AbstractHttpConfigurer::disable);


        http.rememberMe(AbstractHttpConfigurer::disable);
        http.formLogin(AbstractHttpConfigurer::disable);

//        SpringBoot 会自动注入，这里并不需要再次添加
//        http.addFilterAt(aapf,UsernamePasswordAuthenticationFilter.class);
        http.logout(conf -> {
            conf.logoutUrl("/logout")
                    .deleteCookies(authenticationProperties.getRememberMeCookieName())
                    .clearAuthentication(true)
                    .invalidateHttpSession(true)
                    .addLogoutHandler(logoutHandler)
                    .logoutSuccessUrl("/");
        });

        http
                .authorizeHttpRequests(conf -> {
                    conf.anyRequest().permitAll();
                });

        DefaultSecurityFilterChain build = http.build();
        return build;
    }


}
